package ch.fhnw.apsi.templates;

import java.util.LinkedHashMap;
import java.util.Map.Entry;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;

public class HtmlUtils {

  private final static LinkedHashMap<String, String> htmlSpecialChars = new LinkedHashMap<>();
  static {
    // Order of input is relevant!
    htmlSpecialChars.put("&", "&amp;");
    htmlSpecialChars.put("<", "&lt;");
    htmlSpecialChars.put(">", "&gt;");
    htmlSpecialChars.put("\"", "&quot;");
    htmlSpecialChars.put("'", "&#39;"); // &apos;
  }

  public static void escape(@Nonnull final StringBuilder str) {
    mapReplace(str, htmlSpecialChars, FormatFunction.RAW);
  }

  @CheckReturnValue
  public static String escape(@Nonnull final String str) {
    final StringBuilder sb = new StringBuilder(str);
    mapReplace(sb, htmlSpecialChars, FormatFunction.RAW);
    return sb.toString();
  }

  @CheckReturnValue
  public static String linebreak(@Nonnull final String str) {
    return str.replaceAll("\\r\\n?|\\n", "<br/>");
  }

  private enum FormatFunction {
    RAW, HTML_ESCAPE, HTML_ESCAPE_LINEBREAK;
    String format(final String string) {
      switch (this) {
      case RAW:
        return string;
      case HTML_ESCAPE:
        return escape(string);
      case HTML_ESCAPE_LINEBREAK:
        return linebreak(escape(string));
      default:
        assert false : "Case not defined: " + this.name();
        return string;
      }
    }
  }

  /**
   * Replace all keys in the map with their value. f.format(X) is always applied to the value.
   * */
  private static void mapReplace(@Nonnull final StringBuilder sb, @Nonnull final LinkedHashMap<String, String> map,
      final FormatFunction f) {
    int indexOf = 0;
    for (final Entry<String, String> entry : map.entrySet()) {
      final String key = entry.getKey();
      final String value = f.format(entry.getValue());
      while ((indexOf = sb.indexOf(key, indexOf)) > -1) {
        sb.replace(indexOf, indexOf + key.length(), value);
        indexOf += value.length();
      }
    }
  }

  /**
   * Replace all keys in the map with their value. The value is html-escaped for maximum security. Note that this method
   * isn't very well done. For example the values might contain further key and then it depends on the ordering in the
   * map if they get replaced again. But this is good enough for this assignment.
   * 
   * @param sb
   *          StringBuilder containing some HTML.
   * @param map
   *          Mapping of keywords to their replacements (as plain text).
   */
  public static void mapReplace(@Nonnull final StringBuilder sb, @Nonnull final LinkedHashMap<String, String> map) {
    mapReplace(sb, map, FormatFunction.HTML_ESCAPE_LINEBREAK);
  }
}
